﻿#region Copyright 2011 Mike Goatly
// This source is subject to the the MIT License (MIT)
// All rights reserved.
#endregion

namespace StackOverflowSample
{
    #region Using Statements
    using System;
    using System.ComponentModel;
    using System.Diagnostics;
    using System.Globalization;
    using System.IO;
    using System.IO.Compression;
    using System.Linq;
    using System.Reflection;
    using System.Windows;
    using System.Windows.Controls;
    using System.Xml.Linq;
    using Lifti;
    using Lifti.Querying; 
    #endregion

    /// <summary>
    /// Interaction logic for MainWindow.xaml
    /// </summary>
    public partial class MainWindow : Window
    {
        #region Private Variables
        /// <summary>
        /// The full text index of questions, indexed against the text in their body.
        /// </summary>
        private FullTextIndex<Question> questionIndex; 
        #endregion

        /// <summary>
        /// Initializes a new instance of the <see cref="MainWindow"/> class.
        /// </summary>
        public MainWindow()
        {
            this.InitializeComponent();

            this.IsEnabled = false;
            this.queryTextEquivalent.Text = "Indexing 1000 sample questions... please wait...";
            var worker = new BackgroundWorker();
            worker.DoWork += (s, ea) => this.IndexData();
            worker.RunWorkerCompleted += (s, ea) =>
            {
                this.searchText.Text = String.Empty;
                this.searchButton.Click += this.SearchButtonClick;
                this.results.SelectionChanged += this.ResultsSelectionChanged;

                this.queryTextEquivalent.Text = String.Empty;
                this.IsEnabled = true;
            };

            worker.RunWorkerAsync();
        }

        /// <summary>
        /// Loads the meta Stack Overflow data from the compressed file and populates the index.
        /// </summary>
        private void IndexData()
        {
            using (var stream = Assembly.GetExecutingAssembly().GetManifestResourceStream(this.GetType(), "metastackoverflow.data"))
            {
                using (var zipStream = new GZipStream(stream, CompressionMode.Decompress))
                {
                    using (var reader = new StreamReader(zipStream))
                    {
                        var doc = XDocument.Load(reader);

                        // Create the index, specifying that an advanced LIFTI query parser should be used
                        // as well as a stemming word splitter, so we can search easily for word derivatives
                        this.questionIndex = new FullTextIndex<Question>()
                        {
                            QueryParser = new LiftiQueryParser(),
                            WordSplitter = new StemmingWordSplitter()
                        };

                        var entries = doc.Descendants("Entry")
                            .Select(e => new Question() { Title = e.Element("Title").Value, Body = e.Element("Body").Value });

                        this.questionIndex.Index(entries, e => e.Body);
                    }
                }
            }
        }

        /// <summary>
        /// Called when the selection changes in the results list - used to display the selected item's body in the 
        /// browser control.
        /// </summary>
        /// <param name="sender">The sender.</param>
        /// <param name="e">The <see cref="System.Windows.Controls.SelectionChangedEventArgs"/> instance containing the event data.</param>
        private void ResultsSelectionChanged(object sender, SelectionChangedEventArgs e)
        {
            var currentItem = this.results.SelectedItem as Question;
            this.bodyViewer.NavigateToString(currentItem == null ? "<p></p>" : currentItem.Body);
        }

        /// <summary>
        /// Called when the search button is clicked.
        /// Note that normally you would just need to make a call like this:
        /// var searchResults = this.questionIndex.Search(this.searchText.Text).ToArray();
        /// but we are going to parse it manually to show what it looks like.
        /// </summary>
        /// <param name="sender">The sender.</param>
        /// <param name="e">The <see cref="System.Windows.RoutedEventArgs"/> instance containing the event data.</param>
        private void SearchButtonClick(object sender, RoutedEventArgs e)
        {
            try
            {
                if (this.searchText.Text.Length > 0)
                {
                    var sw = new Stopwatch();
                    sw.Start();

                    // Use the query parser to parse the query into an object representation
                    var query = this.questionIndex.QueryParser.ParseQuery(this.searchText.Text, this.questionIndex.WordSplitter);

                    // Store how long it took to parse the query
                    long queryParseTime = sw.ElapsedMilliseconds;
                    sw.Reset();
                    sw.Start();

                    // Display the textual equivalent of the query
                    this.queryTextEquivalent.Text = query.ToString();

                    // Execute the query
                    var searchResults = this.questionIndex.Search(query).ToArray();

                    // Show the stats
                    this.stats.Text = String.Format(
                        CultureInfo.CurrentCulture,
                        "Query parse time: {0}ms - Search time: {1}ms",
                        queryParseTime,
                        sw.ElapsedMilliseconds);

                    // Show the results
                    this.results.ItemsSource = searchResults;
                }
                else
                {
                    this.results.ItemsSource = null;
                }
            }
            catch (QueryParserException ex)
            {
                this.results.ItemsSource = null;
                this.bodyViewer.NavigateToString("<h2>Query parser error:</h2><p style='color:red'>" + ex.Message + "</p>");
            }
        }
    }
}
